/**
 * 							SYSTEM MEMEORY HEAP MANAGEMENT
 * @brief		OS kernel memory heap.
 * @author		chy.
 * @note		this file manage the virtual memory heap of OS!
 * @comment		We'd better manage memory of OS.Should avoid
 * 				wasting memory as OS would use memory by page
 * @warning	  =>We always virtual memory here!
 */

#ifndef _K_MM_OS_HEAP_C_
#define	_K_MM_OS_HEAP_C_

#include "k_mm_heap.h"
#include "mem_basedefine.h"
#include "../k_common_asm.h"
#include "k_mem_sysmmu.h"

/**
 * 								GLOBALS
 */
K_HEAPPOOL *g_os_heappool;		// heap pool of OS kernel

/**
 * @BRIEF	: Create new heap
 * @PARAM	: size in byte
 * @RETRUN	: 0 failed. 1 success
 * @WARNING	: virtual memory
 */
static int k_mm_os_createheap(unsigned int size)
{
	// we allocate new virtual page...in OS mode!
	K_PHEAP p_heap_new;
	K_PHEAPBLOCK p_block;

	// important
	size += K_HEAP_HEADERSIZE + K_HEAPBLOCK_HEADERSIZE;

	// check whether we can continue allocate new heap
/*	if(g_os_heappool->hpool_size >= K_SYS_OS_HEAP_MAXSIZE)
	{
		// we limited the heap pool size
		return 0;
	}
*/
	K_BOUND_4KB(size);
	size = size >> 12;

	p_heap_new = (K_PHEAP)k_mem_mmu_allocate(K_MEM_PHY_SYSTEM, size);

	if(p_heap_new == 0)
	{
		// we failed
		return 0;
	}

	p_heap_new->heap_totalsize = size << 12;
	p_heap_new->heap_usedsize  = K_HEAP_HEADERSIZE + K_HEAPBLOCK_HEADERSIZE;
	p_heap_new->heap_gcfreq = 0;

	// create default heap block K_HEAP_HEADERSIZE
	p_block = (K_PHEAPBLOCK)((unsigned int)p_heap_new + K_HEAP_HEADERSIZE);
	p_block->hb_mflag = K_HEAPBLOCK_HEADERFLAG_FREE;
	p_block->hb_next  = 0;
	p_block->hb_parent = p_heap_new;
	p_block->hb_size = (size << 12) - K_HEAP_HEADERSIZE - K_HEAPBLOCK_HEADERSIZE;

	p_heap_new->heap_firsthb = p_block;

	/** add this to OS heap pool .. we need to disable interrupt **/
	K_MEM_DISABLE_INTERRUPT;
	p_heap_new->heap_next = g_os_heappool->hp_firstheap;
	g_os_heappool->hp_firstheap = p_heap_new;
	g_os_heappool->hp_heapcount++;
	g_os_heappool->hpool_size += size << 12;
	K_MEM_ENABLE_INTERRUPT;
	return 1;
}

/**
 * @BRIEF	: Allocate memory from heap of OS
 * @PARAM	: size 		size of memory in byte
 * @RETRUN	: memory pointer
 * @WARNING	: virtual memory
 */
void *k_mm_os_heap_allocate(unsigned int size)
{
    if(size == 0)
    {
        return 0;
    }

	do{
		K_MEM_DISABLE_INTERRUPT;
		K_PHEAP	heap_cur = g_os_heappool->hp_firstheap;
		for(; heap_cur != 0; heap_cur = heap_cur->heap_next)
		{
			// first we check the free size of current heap.
			// we should not find the heap block in current heap if free size of it is not enough
			// This can make scan faster!
			if(size > (heap_cur->heap_totalsize - heap_cur->heap_usedsize))
			{
				// we just find the next heap!
				continue;
			}
			else
			{
				// it looks like there enough memory we can use
				// do next deeply checking
				// checking every blocks
				K_PHEAPBLOCK hb_cur = heap_cur->heap_firsthb;
				K_PHEAPBLOCK hb_pre = 0;
				for(;hb_cur != 0; hb_cur = hb_cur->hb_next)
				{
					// we should disable interrupt
					if(hb_cur->hb_mflag != K_HEAPBLOCK_HEADERFLAG_FREE)
					{
						// maybe other thread has allocated it
						continue;
					}


					if(size >= (hb_cur->hb_size - K_HEAPBLOCK_HEADERSIZE) && size <= hb_cur->hb_size )
					{
						// size of current block is just to be used comfortably
						// we can allocate it.This can avoid pieces of memory
						// we move this block out of block list
						heap_cur->heap_usedsize += hb_cur->hb_size;
						if(hb_pre == 0)
						{
							heap_cur->heap_firsthb = hb_cur->hb_next;
						}
						else
						{
							hb_pre->hb_next = hb_cur->hb_next;
						}
						hb_cur->hb_mflag = K_HEAPBLOCK_HEADERFLAG_USING;
						K_MEM_ENABLE_INTERRUPT;
						return	(void *)((unsigned int)hb_cur + K_HEAPBLOCK_HEADERSIZE);
					}
					if(size > hb_cur->hb_size )
					{
						// this block is not enough
						hb_pre = hb_cur;
						continue;
					}
					if(size < (hb_cur->hb_size - K_HEAPBLOCK_HEADERSIZE))
					{
						// we allocate from the bottom of current block
						// we should allocate more K_HEAPBLOCK_HEADERSIZE bytes
						K_PHEAPBLOCK	p_hb_new;
						unsigned int start_addr = (unsigned int)hb_cur + hb_cur->hb_size - size;
						heap_cur->heap_usedsize += size + K_HEAPBLOCK_HEADERSIZE;
						hb_cur->hb_size -= size + K_HEAPBLOCK_HEADERSIZE;

						// we should not add new block to list...because it will be using...
						p_hb_new = (K_PHEAPBLOCK)start_addr;

						p_hb_new->hb_parent = heap_cur;
						p_hb_new->hb_size = size;
						p_hb_new->hb_mflag = K_HEAPBLOCK_HEADERFLAG_USING;	// set flag

						K_MEM_ENABLE_INTERRUPT;
						return	(void *)(start_addr + K_HEAPBLOCK_HEADERSIZE);
					}
					hb_pre = hb_cur;
				}
			}
		}
		K_MEM_ENABLE_INTERRUPT;
	}
	while(k_mm_os_createheap(size));// we still can't find.shit we need to create new heap now..after it, re-find..
	return 0;		// sorry, i have nothing to do more....:((
}

/**
 * @BRIEF	: Free memory to heap of OS
 * @PARAM	: mem_addr		address of memory to free
 * @RETRUN	: void
 * @WARNING	: virtual memory
 */
void k_mm_os_heap_free(void *mem_addr)
{
	K_PHEAPBLOCK block = (K_PHEAPBLOCK)((unsigned int)mem_addr - K_HEAPBLOCK_HEADERSIZE);

	// check whether dirty
	if(block->hb_mflag != K_HEAPBLOCK_HEADERFLAG_USING)
	{
		// .... shit....
		// I don't know what should do now!
		return;
	}
	block->hb_mflag = K_HEAPBLOCK_HEADERFLAG_FREE;	// set flag.
	// we add current block to heap list which it is belonging to
	K_MEM_DISABLE_INTERRUPT;
	block->hb_next = ((K_PHEAP)block->hb_parent)->heap_firsthb;
	((K_PHEAP)block->hb_parent)->heap_gcfreq++;
	((K_PHEAP)block->hb_parent)->heap_firsthb = block;
	((K_PHEAP)block->hb_parent)->heap_usedsize -= block->hb_size;

	// check whether we need to GC current heap
	if(((K_PHEAP)block->hb_parent)->heap_gcfreq > K_SYS_OS_HEAP_GCFREQ)
	{
		k_mm_heap_gc((K_PHEAP)block->hb_parent);
	}

    // check whether we need to free this page
    if(g_os_heappool->hpool_size > K_SYS_OS_HEAP_MAXSIZE)
    {
        // we need to check if there's any heap we can free
        k_mm_heap_freeheap(g_os_heappool);
    }

	K_MEM_ENABLE_INTERRUPT;
}

/**
 * @BRIEF	: GC for a heap
 * @PARAM	: heap
 * @RETRUN	: inc bytes
 * @NOTE	: if GC success, K_SYS_OS_HEAP_GCFREQ of heap will clear to 0
 */
/*static int	k_mm_os_heap_gc(K_PHEAP heap)
{
	char *p_tmp = (char *)heap;
	unsigned int oldsize = heap->heap_usedsize;
	K_PHEAPBLOCK p_pre_block,p_cur_block,p_next_block,p_block;

	if(heap->heap_gcfreq < K_SYS_OS_HEAP_GCFREQ)
	{
		return 0;
	}

	p_block 	= heap->heap_firsthb;
	do{
		p_cur_block = p_block->hb_next;
		p_pre_block = p_block;

		while(p_cur_block != 0)
		{
			if(((unsigned int)p_block + p_block->hb_size + K_HEAPBLOCK_HEADERSIZE)
					== (unsigned int)p_cur_block)
			{
				p_pre_block->hb_next = p_cur_block->hb_next;
				heap->heap_usedsize -= K_HEAPBLOCK_HEADERSIZE;
				p_block->hb_size += p_cur_block->hb_size + K_HEAPBLOCK_HEADERSIZE;
				p_cur_block = p_cur_block->hb_next;
			}
			else
			{
				p_pre_block = p_cur_block;
				p_cur_block = p_cur_block->hb_next;
			}
		}
	}while(p_block = p_block->hb_next);
	heap->heap_gcfreq = 0;
	return oldsize - heap->heap_usedsize;
}*/

/**
 * @BRIEF	: free a heap
 * @PARAM	: void
 * @RETRUN	: void
 * @NOTE	: this function will scan the heap pool of OS, to find
 *          : which heap can be freed.and free it.
 *          : just free one heap
 */
/*static void k_mm_os_heap_freeosheap(void)
{
    K_PHEAP heap_cur = g_os_heappool->hp_firstheap;
    K_PHEAP heap_pre = 0;

    while(heap_cur != 0)
    {
        // there is only one free heap block, and
        // this block is unused. now we can free
        // current heap.
        if(heap_cur->heap_usedsize == (K_HEAP_HEADERSIZE + K_HEAPBLOCK_HEADERSIZE))
        {
            if(heap_pre == 0)
            {
                // we free the first heap in heap pool
                g_os_heappool->hp_firstheap = heap_cur->heap_next;
            }
            else
            {
                heap_pre->heap_next = heap_cur->heap_next;
            }
            g_os_heappool->hp_heapcount--;
            g_os_heappool->hpool_size -= heap_cur->heap_totalsize;
            k_mem_mmu_free(heap_cur);
            return;
        }
        heap_pre = heap_cur;
        heap_cur = heap_cur->heap_next;
    }
}
*/
#endif	/* _K_MM_OS_HEAP_C_ */
